package de.justphil.tcg.tcgserver.rest.db;

import static de.justphil.tcg.tcgserver.commons.logging.TCGLogging.restLogger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.ws.rs.core.Response;

import org.slf4j.Logger;

import com.hazelcast.core.Transaction;

import de.justphil.tcg.tcgserver.commons.domain.Card;
import de.justphil.tcg.tcgserver.commons.domain.CardInstance;
import de.justphil.tcg.tcgserver.commons.domain.Deck;
import de.justphil.tcg.tcgserver.commons.domain.User;
import de.justphil.tcg.tcgserver.commons.domain.wrappers.CreateDeck;
import de.justphil.tcg.tcgserver.commons.exceptions.TCGException;
import de.justphil.tcg.tcgserver.commons.grid.Grid;
import de.justphil.tcg.tcgserver.commons.i18n.Error;
import de.justphil.tcg.tcgserver.commons.management.DataManager;

public class DeckCreator {
	private static final Logger log = restLogger(DeckCreator.class);
	
	
	
	
	public Deck createDeck(EntityManagerFactory factory, DataManager dataManager, User user, CreateDeck createDeck) throws TCGException {
		log.debug("createDeck()");
		
		// Create EntityManager
		EntityManager em = factory.createEntityManager();
		
		// Begin JPA transaction
		em.getTransaction().begin();
		
		// Begin Hazelcast transaction
		Transaction txn = Grid.getTransaction();
		txn.begin();
		
		try {
			
			Deck deck = doCreateDeck(dataManager, user, createDeck, em);
			
			// Commit transactions
			txn.commit();
			em.getTransaction().commit();
			
			return deck;
			
		}
		catch (TCGException e) {
			// Expected exception
			
			// Rollback transactions
			rollback(em, txn);
			
			// Rethrow this exception
			throw e;
			
		}
		catch (Throwable t) {
			// Unexpected exception
			
			// Print message and stack trace
			log.error("createDeck() ## Unexpected Throwable: {}", t.getMessage());
			
			t.printStackTrace();
			
			// Rollback transactions
			rollback(em, txn);
			
			// Throw TCGException
			throw new TCGException(Error.SERVER_ERROR_INTERNAL, Response.Status.INTERNAL_SERVER_ERROR);
			
		}
		finally {
			em.close();
		}
		
	}
	
	private void rollback(EntityManager em, Transaction txn) {
		txn.rollback();
		em.getTransaction().rollback();
	}
	
	private Deck doCreateDeck(DataManager dataManager, User user, CreateDeck createDeck, EntityManager em) throws TCGException {
		
		Deck deck = new Deck(createDeck.getName(), user.getId());
		
		em.persist(deck);
		em.flush();
		
		
		Card card = null;
		CardInstance ci = null;
		long[] cardInstances = createDeck.getCardInstances();
		for (int i = 0; i < cardInstances.length; i++) {
			ci = dataManager.getCardInstanceById( cardInstances[i] );
			
			// ci -> deck
			ci.getDecks().add(deck.getId());
			
			// deck -> ci
			deck.getCardInstances().add(ci);
			
			// update ci in the "decks" IMap and at other locations in the "users" IMap (decks sub objects)
			for (Deck d : user.getDecks()) {
				
				/*
				otherLocationDeck = dataManager.getDeckById(d.getId());
				
				if (otherLocationDeck.getCardInstances().remove(ci)) {
					otherLocationDeck.getCardInstances().add(ci);
					dataManager.updateDeck(otherLocationDeck.getId(), otherLocationDeck);
					log.debug("Replaced card instance {} from decks's {} CIs with updated version.", ci.getId(), otherLocationDeck.getId());
				}
				*/
				
				if (d.getCardInstances().remove(ci)) {
					d.getCardInstances().add(ci);
					log.debug("Replaced card instance {} from user decks's {} CIs with updated version.", ci.getId(), d.getId());
				}
			}
			
			// update ci occurrences at other locations in the "users" IMap (card instances sub objects)
			if (user.getCardInstances().remove(ci)) {
				user.getCardInstances().add(ci);
				
				log.debug("Replaced card instance {} from user's {} CIs with updated version.", ci.getId(), user.getId());
			}
			
			// update ci in the "cards" IMap
			card = dataManager.getCardById( ci.getCardId() );
			if (card.getCardInstances().remove(ci)) {
				card.getCardInstances().add(ci);
				
				log.debug("Replaced card instance {} from card's {} CIs with updated version.", ci.getId(), card.getId());
			}
			dataManager.updateCard(card.getId(), card);
			
			// update ci in the "cardInstances" IMap
			dataManager.updateCardInstance(ci.getId(), ci);
		}
		
		// user -> deck
		user.getDecks().add(deck);
		
		dataManager.updateUser(user.getId(), user);
		
		dataManager.insertDeck(deck.getId(), deck);
		
		return deck;
	}

}
